package gate

import (
	"crypto/cipher"
	"crypto/des"
	"encoding/hex"
	"fmt"
	"github.com/liu-junyong/go-logger/logger"
	. "github.com/liu-junyong/gohelper"
	"gopkg.in/chanxuehong/wechat.v2/mp/oauth2"
	mpoauth2 "gopkg.in/chanxuehong/wechat.v2/mp/oauth2"
	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
	"io/ioutil"
	"log"
	"regexp"
	"strconv"
	"strings"
	"time"
	"unicode"
	"encoding/json"
	"github.com/larspensjo/config"
	"math/rand"
	"net/http"
	"os"
	"path/filepath"
	"sync"
)

type AreaConfig struct {
	Area           string
	MongoURL       string
	Paomadeng      string
	Free           string
	Notice         string
	MgoSession     *mgo.Session
	WXAppID        string
	WXAppSecret    string
	XLAppId        string
	XLAppSecret    string
	Oauth2Endpoint *oauth2.Endpoint
	InitCard       int
	DisableUserReg bool
	Feature        string //微信的特征识别
	GiveCard       int
}

var g_wsWRlock *sync.RWMutex
var g_areas map[string]*AreaConfig
var g_CacheMap sync.Map
var g_DecSecret = []byte{0x26, 0xa4, 0xb7, 0x28, 0x65, 0xf0, 0x36, 0x56}

type CacheItem struct {
	OpenId      string
	Nick        string
	OriginalPic string
	SmallPic    string
	Timestamp   int64
}

func InitConfig() {
	g_wsWRlock = &sync.RWMutex{}
	g_areas = make(map[string]*AreaConfig)
	dir, _ := filepath.Abs(filepath.Dir(os.Args[0]))
	filename := dir + "/gate.ini"
	cfg, e := config.ReadDefault(filename)
	if e != nil {
		log.Fatalln("读取配置文件错误：", e)
	}

	if cfg != nil {
		Areas, err11 := cfg.String("config", "areas")
		log.Println(Areas, err11)

		as := strings.Split(Areas, ",")
		for _, area := range as {
			config := AreaConfig{}
			config.Area = area

			config.MongoURL, _ = cfg.String(area, "url")
			config.Paomadeng, _ = cfg.String(area, "paomadeng")
			config.Notice, _ = cfg.String(area, "notice")
			config.Free, _ = cfg.String(area, "free")
			config.WXAppID, _ = cfg.String(area, "wxAppId")
			config.WXAppSecret, _ = cfg.String(area, "wxSecret")
			config.XLAppId, _ = cfg.String(area, "xlAppId")
			config.XLAppSecret, _ = cfg.String(area, "xlSecret")
			config.DisableUserReg, _ = cfg.Bool(area, "disableUserReg")
			config.Feature, _ = cfg.String(area, "feature")
			config.GiveCard, _ = cfg.Int(area, "day_give")
			config.InitCard, _ = cfg.Int(area, "init")
			if config.InitCard == 0 {
				config.InitCard = 5
			}

			logger.Info(config)
			config.Oauth2Endpoint = mpoauth2.NewEndpoint(config.WXAppID, config.WXAppSecret)

			g_areas[area] = &config
		}

	}

}

func MongoSession(cfg *AreaConfig) *mgo.Session {
	defer func() {
		if r := recover(); r != nil {
			logger.Error(r)
		}
	}()

	if cfg.MgoSession == nil {
		var err error
		info, _ := mgo.ParseURL(cfg.MongoURL)
		cfg.MgoSession, err = mgo.DialWithInfo(info)
		if err != nil {
			panic(err)
		}

	}
	return cfg.MgoSession.Clone()
}

func Get_AreaConfig(area string) *AreaConfig {
	g_wsWRlock.RLock()
	defer g_wsWRlock.RUnlock()
	ret1 := g_areas[area]
	if ret1 == nil {
		return g_areas["default"]
	}
	return ret1
}

func GetUserInfo(config *AreaConfig, userid int) string {
	session := MongoSession(config)
	db := session.DB("")             //数据库名称
	collection := db.C("mj_players") //如果该集合已经存在的话，则直接返回

	defer func() {
		session.Close()
		if err := recover(); err != nil {
			logger.Error(err)
		}
	}()

	result := make(map[string]interface{}, 0)

	err := collection.Find(&bson.M{"_id": userid}).One(&result)
	if err != nil {
		logger.Error(err)
		return ""
	}

	delete(result, "openid")
	delete(result, "unionid")
	delete(result, "sign")
	delete(result, "time")
	lv := GenLevel(&result)
	delete(result, "lv")
	delete(result, "xianliao")
	result["ts"] = lv

	jsonData, err := json.Marshal(result)
	return string(jsonData)
}

func GetUserUnionID(config *AreaConfig, userid int) string {
	session := MongoSession(config)
	db := session.DB("")             //数据库名称
	collection := db.C("mj_players") //如果该集合已经存在的话，则直接返回

	defer func() {
		session.Close()
		if err := recover(); err != nil {
			logger.Error(err)
		}
	}()

	result := make(map[string]interface{}, 0)

	err := collection.Find(&bson.M{"_id": userid}).One(&result)
	if err != nil {
		logger.Error(err)
		return fmt.Sprintf("找不到UserID:%d", userid)
	}

	jsonData, err := json.Marshal(result)
	return string(jsonData)
}

func GetUserCard(config *AreaConfig, union_id string, key string) string {
	session := MongoSession(config)
	db := session.DB("")             //数据库名称
	collection := db.C("mj_players") //如果该集合已经存在的话，则直接返回

	defer func() {
		session.Close()
		if err := recover(); err != nil {
			logger.Error(err)
		}
	}()

	result := make(map[string]interface{}, 0)
	opt := bson.M{"_id": 1, "numof_cards_1": 1, "numof_cards_2": 1, "numof_cards_3": 1, key: 1}
	err := collection.Find(&bson.M{"unionid": union_id}).Select(&opt).One(&result)
	if err != nil {
		logger.Error(err)
		return "{}"
	}

	result["msg"] = "分享成功，房卡已经奉上，请查收"
	jsonData, err := json.Marshal(result)
	return string(jsonData)
}

func GetUserInfoFromXianLiao(config *AreaConfig, info *CacheItem) (string, bool) {
	session := MongoSession(config)
	db := session.DB("")             //数据库名称
	collection := db.C("mj_players") //如果该集合已经存在的话，则直接返回

	defer func() {
		session.Close()
		if err := recover(); err != nil {
			logger.Error(err)
		}
	}()

	result := make(map[string]interface{}, 0)
	var err error

	err = collection.Find(&bson.M{"xianliao.openId": info.OpenId}).One(&result)
	if err != nil {
		if err.Error() != "not found" {
			logger.Error(err)
		}

		return "", false
	}

	update := false
	if info.Nick != result["xianliao"].(map[string]interface{})["nick"] {
		update = true
	}
	if info.OriginalPic != result["xianliao"].(map[string]interface{})["originalPic"] {
		update = true
	}
	if info.SmallPic != result["xianliao"].(map[string]interface{})["smallPic"] {
		update = true
	}

	if update {
		err := collection.Update(&bson.M{"xianliao.openId": info.OpenId}, &bson.M{"$set": bson.M{"xianliao.nick": info.Nick, "xianliao.originalPic": info.OriginalPic, "xianliao.smallPic": info.SmallPic}})
		if nil != err {
			logger.Error(err)
		}
	}

	delete(result, "sign")
	delete(result, "time")
	lv := GenLevel(&result)
	delete(result, "lv")
	result["ts"] = lv
	result["result"] = 0

	jsonData, err := json.Marshal(result)
	return string(jsonData), true
}

func SaveUserInfo(info *map[string]interface{}, unionid string, fake bool, wx bool) *map[string]interface{} {

	config := Get_AreaConfig("default")

	session := MongoSession(config)
	db := session.DB("")             //数据库名称
	collection := db.C("mj_players") //如果该集合已经存在的话，则直接返回

	defer func() {
		session.Close()
		if err := recover(); err != nil {
			logger.Error(err)
		}
	}()

	result2 := make(map[string]interface{}, 0)
	err5 := collection.Find(&bson.M{"unionid": unionid}).One(&result2)

	if err5 != nil && (wx || !config.DisableUserReg) {

		getNextSeq := func() func() int {
			counter := db.C("id_generator")
			cid := "mj_player_id"
			return func() int {
				change := mgo.Change{
					Update:    bson.M{"$inc": bson.M{"id": 1}},
					Upsert:    true,
					ReturnNew: true,
				}
				doc := struct{ Id int }{}
				if _, err := counter.Find(bson.M{"_id": cid}).Apply(change, &doc); err != nil {
					log.Printf("%v 生成ID错误 ", err)
					panic(fmt.Errorf("get counter failed:", err.Error()))
				}
				logger.Info("id:", doc)
				return doc.Id
			}
		}()

		if result2["unionid"] == nil {

			initcard := config.InitCard
			if fake {
				initcard = 0
			}

			_row := map[string]interface{}{
				"_id":           getNextSeq(),
				"unionid":       unionid,
				"openid":        unionid,
				"numof_cards_2": initcard,
				"numof_cards_1": 0,
				"numof_cards_3": 0,
				"referrer_uid":  0,
				"create_time":   time.Now().Unix(),
			}
			if err := collection.Insert(_row); err != nil {
				log.Printf("insert %v failed: %vn", _row, err)
			} else {
				log.Printf("insert: %v success!n", _row)
			}
		}

	}

	strSex := (*info)["sex"]
	sex := 0

	if strSex != nil {
		switch strSex.(type) {
		case float64:
			sex = int(strSex.(float64))
		case string:
			sex, _ = strconv.Atoi(strSex.(string))
		}
	}

	if result2["last_login_time"] != nil {
		llt := result2["last_login_time"].(int64)
		login_second := time.Now().Unix() - llt
		if login_second > (86400*30) && login_second < (86400*600) {
			(*info)["referrer_uid"] = 0
		}
	}

	disable := false
	if result2["disable"] != nil {
		disable = result2["disable"].(bool)
	}

	delete((*info), "sex")
	(*info)["sex"] = int32(sex)
	delete((*info), "numof_cards_1")
	delete((*info), "numof_cards_2")
	delete((*info), "numof_cards_3")

	AsynUpdateDB(config, info, unionid)

	result := make(map[string]interface{}, 0)
	err2 := collection.Find(&bson.M{"unionid": unionid}).One(&result)
	//	err2 := collection.Find(&key).One(&result)
	if err2 != nil {
		logger.Error(err2)
		return nil
	}

	delete(result, "openid")
	delete(result, "unionid")
	result["notice"] = config.Notice
	result["paomadeng"] = config.Paomadeng
	result["free"] = config.Free
	result["area"] = config.Area

	roomtimestr := result["lastRoomTime"]
	roomtime := int64(0)
	if roomtimestr != nil {
		roomtime = roomtimestr.(int64)
	}
	nowtime := time.Now().Unix()
	if nowtime-roomtime > 36000 { //10分钟内
		delete(result, "lastRoom")
	}
	lv := GenLevel(&result)
	delete(result, "lv")
	result["ts"] = lv

	token := GenToken(&result)
	result["token"] = token
	if disable {
		delete(result, "token")
		delete(result, "_id")
	}
	return &result

}

func GenLevel(user *map[string]interface{}) string {
	defer func() {
		if r := recover(); r != nil {
			logger.Error(r)
		}
	}()

	lv := 0
	if (*user)["lv"] != nil {
		lv = (*user)["lv"].(int)
	}

	tick := time.Now().Unix()
	lv = lv % 9

	return fmt.Sprintf("%d%d%d%d", tick, lv, rand.Intn(9), rand.Intn(9))
}

func GenToken(user *map[string]interface{}) string {
	userid := (*user)["_id"].(int)
	username := (*user)["nickname"].(string)
	area := (*user)["area"].(string)
	tick := time.Now().Unix()

	tokenbefor := fmt.Sprintf("%d/%s/%d/%s", userid, username, tick, area)
	aesEnc := AesEncrypt{}
	strKey := "liu.junyong@gmail.com"
	aesEn, err2 := aesEnc.Encrypt(tokenbefor, strKey)
	if err2 != nil {
		logger.Error(err2)
	}
	tokenafer := hex.EncodeToString(aesEn)
	return tokenafer
}

func AsynUpdateDB(config *AreaConfig, info *map[string]interface{}, unionid string) {
	end := make(chan bool)
	ch := make(chan bool)
	go func() {
		defer func() { end <- true }()
		select {
		case <-ch:
		case <-time.After(time.Second * 2):
		}
	}()
	go func() {
		session := MongoSession(config)
		session.SetSocketTimeout(time.Second * 15)
		db := session.DB("")             //数据库名称
		collection := db.C("mj_players") //如果该集合已经存在的话，则直接返回
		defer func() {
			session.Close()
			if err := recover(); err != nil {
				logger.Error(err)
			}
		}()

		keys := bson.M{"$set": info}
		err := collection.Update(&bson.M{"unionid": unionid}, &keys)
		if err != nil { //写到本地文本里面
			logger.Error(err)
		}

	}()
	<-end
}

type ApplePayStruct struct {
	Ticket    string `json:ticket`
	Area      string `json:area`
	Appid     int32  `json:appid`
	Playerid  int32  `json:playerid`
	Cid       string `json:cid`
	Timestamp string `json:timestamp`
	Sign      string `json:sign`
}

func (this *ApplePayStruct) IsInvalid() bool {
	return true
}

func IncUserRoomCard(UserID int32, config *AreaConfig, CardCount int32) {

	defer func() {
		if r := recover(); r != nil {
			logger.Error(r)
		}
	}()

	if CardCount <= 0 {
		CardCount = 1
	}

	session := MongoSession(config)
	db := session.DB("")             //数据库名称
	collection := db.C("mj_players") //如果该集合已经存在的话，则直接返回
	err := collection.Update(bson.M{"_id": UserID}, &bson.M{"$inc": bson.M{"numof_cards_2": CardCount}})
	if err != nil {
		//写到本地文本里面
		logger.Error(err)
	}

}

func AddCard2Player(this *ApplePayStruct) {
	cardcount := int32(0)
	if this.Cid == "iap_fangka_2001" {
		cardcount = 2
	}
	if this.Cid == "iap_fangka_2002" {
		cardcount = 10
	}
	if this.Cid == "iap_fangka_2003" {
		cardcount = 100
	}
	config := Get_AreaConfig(this.Area)

	IncUserRoomCard(this.Playerid, config, cardcount)

}

func GetXLToken(config *AreaConfig, code string) string {
	defer func() {
		if r := recover(); r != nil {
			logger.Error(r)
		}
	}()

	tokenUrl := "https://ssgw.updrips.com/oauth2/accessToken"
	tokenRequestBody := "appid=" + config.XLAppId + "&appsecret=" + config.XLAppSecret + "&grant_type=authorization_code&code=" + code

	tokenRequest, err1 := http.NewRequest("POST", tokenUrl, strings.NewReader(tokenRequestBody))
	if err1 != nil {
		return ""
	}

	tokenRequest.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	resp, err2 := http.DefaultClient.Do(tokenRequest)
	defer resp.Body.Close()
	if err2 != nil {
		return ""
	}

	tokenBody, err3 := ioutil.ReadAll(resp.Body)
	if err3 != nil {
		return ""
	}

	var setList map[string]interface{}
	err4 := json.Unmarshal(tokenBody, &setList)
	if err4 != nil {
		return ""
	}

	if setList["data"] == nil {
		return ""
	}

	dataList := setList["data"].(map[string]interface{})
	if dataList["access_token"] == nil {
		return ""
	}

	xlToken := dataList["access_token"].(string)
	return xlToken
}

func GetXLUserInfo(token string) *CacheItem {
	defer func() {
		if r := recover(); r != nil {
			logger.Error(r)
		}
	}()

	openidUrl := "https://ssgw.updrips.com/resource/user/getUserInfo"
	openidRequestBody := "access_token=" + token
	openidRequest, err1 := http.NewRequest("POST", openidUrl, strings.NewReader(openidRequestBody))
	if err1 != nil {
		return nil
	}
	openidRequest.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	resp1, err2 := http.DefaultClient.Do(openidRequest)
	if err2 != nil {
		return nil
	}

	defer resp1.Body.Close()
	openidBody, err3 := ioutil.ReadAll(resp1.Body)
	if err3 != nil {
		return nil
	}

	var setList map[string]interface{}
	err4 := json.Unmarshal(openidBody, &setList)
	if err4 != nil {
		return nil
	}

	if nil == setList["data"] {
		return nil
	}

	dataList := setList["data"].(map[string]interface{})

	ret := new(CacheItem)
	ret.OpenId = dataList["openId"].(string)
	ret.Nick = dataList["nickName"].(string)

	logger.Error(ret.Nick)
	ret.OriginalPic = dataList["originalAvatar"].(string)
	ret.SmallPic = dataList["smallAvatar"].(string)
	ret.Timestamp = time.Now().Unix()

	return ret
}

func DoRefreshCacheMap() {
	for {
		t := time.Now().Unix()
		g_CacheMap.Range(func(key, value interface{}) bool {
			if t-value.(*CacheItem).Timestamp > 3600 {
				g_CacheMap.Delete(key)
			}
			return true
		})

		time.Sleep(1e9)
	}
}

func insertcard(dt string, unionid string, config *AreaConfig, key string) bool {
	session := MongoSession(config)
	defer func() {
		session.Close()
		if err := recover(); err != nil {
			logger.Error(err)
		}
	}()

	db := session.DB("")             //数据库名称
	collection := db.C("mj_players") //如果该集合已经存在的话，则直接返回
	err := collection.Update(bson.M{"unionid": unionid, key: bson.M{"$ne": dt}},
		&bson.M{"$set": bson.M{key: dt}, "$inc": bson.M{"numof_cards_2": config.GiveCard}})

	if err != nil {
		//写到本地文本里面
		logger.Error(err)
		return false
	}
	return true

}

func BindUser(tuiguang string, UserID int, config *AreaConfig) string {
	session := MongoSession(config)
	defer func() {
		session.Close()
		if err := recover(); err != nil {
			logger.Error(err)
		}
	}()

	db := session.DB("")                            //数据库名称
	collection := db.C("mj_promotion_code_records") //如果该集合已经存在的话，则直接返回

	_row := map[string]interface{}{
		"_id":            UserID,
		"promotion_code": tuiguang,
		"create_time":    time.Now().UnixNano() / 1000000,
	}

	_, err := collection.Upsert(bson.M{"_id": UserID}, _row)
	if err != nil {
		logger.Error(err)
	}

	pid, _ := strconv.Atoi(tuiguang)
	return fmt.Sprintf("{\"result\":0,\"parents_id\":%d}", pid)
}

func BindUserByUnioon(tuiguang string, UnionID string, config *AreaConfig) string {
	session := MongoSession(config)
	defer func() {
		session.Close()
		if err := recover(); err != nil {
			logger.Error(err)
		}
	}()

	pid, _ := strconv.Atoi(tuiguang)
	_row := map[string]interface{}{
		"parent_id":     pid,
		"bindAgentTime": time.Now().Unix(),
	}

	db := session.DB("")             //数据库名称
	collection := db.C("mj_players") //如果该集合已经存在的话，则直接返回
	err := collection.Update(bson.M{"unionid": UnionID}, &bson.M{"$set": _row})
	if err != nil {
		logger.Error(err)
	}

	return "{\"result\":0}"
}

func BindCertificate(UserID int, UnionID string, config *AreaConfig, PRCID string, name string, tel string) string {
	session := MongoSession(config)
	defer func() {
		session.Close()
		if err := recover(); err != nil {
			logger.Error(err)
		}
	}()

	db := session.DB("")                      //数据库名称
	collection1 := db.C("mj_shimingrenzheng") //如果该集合已经存在的话，则直接返回
	_row1 := map[string]interface{}{
		"_id":    UserID,
		"name":   name,
		"idcard": PRCID,
		"phone":  tel,
	}
	_, err2 := collection1.Upsert(bson.M{"_id": UserID}, _row1)
	if err2 != nil {
		logger.Error(err2)
	}

	collection2 := db.C("mj_players") //如果该集合已经存在的话，则直接返回
	_row2 := map[string]interface{}{
		"real_name":        name,
		"identity_card_id": PRCID,
		"phone":            tel,
	}
	err := collection2.Update(bson.M{"unionid": UnionID}, bson.M{"$set": _row2})
	if err != nil {
		logger.Error(err)
	}

	return "{\"result\":0}"
}

func verifyName_begin(name string) bool {
	expr := "^[\\p{Han}]{2,4}$"
	r, _ := regexp.Compile(expr)
	return r.MatchString(name)

}

func verifyTel_begin(name string) bool {
	expr := "^1\\d{10}$"
	r, _ := regexp.Compile(expr)
	return r.MatchString(name)

}

func verifyIdCard_begin(idCard string) bool {
	expr := "(^[1-9]\\d{5}[1-9]\\d{3}((0\\d)|(1[0-2]))(([0|1|2]\\d)|3[0-1])\\d{3}(\\d|x|X)$)|(^[1-9]\\d{7}((0\\d)|(1[0-2]))(([0|1|2]\\d)|3[0-1])\\d{3}$)"

	ret1, _ := VerificateID(idCard)
	if !ret1 {
		return false
	}

	r, _ := regexp.Compile(expr)
	return r.MatchString(idCard)
}

func VerificateID(id string) (bool, error) {
	lastOne, err := getCheckCode(id)
	if err != nil {
		return false, err
	}

	if string(id[17]) == lastOne {
		return true, nil
	}
	return false, nil
}

func getCheckCode(id string) (string, error) {

	var weight = []int{7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2}
	var code = []string{"1", "0", "X", "9", "8", "7", "6", "5", "4", "3", "2"}

	if len(id) != 18 {
		return "", fmt.Errorf("长度不对")
	}

	sum := 0
	for i := 0; i < 17; i++ {
		if unicode.IsDigit(rune(id[i])) {
			n, err := strconv.Atoi(string(id[i]))
			if err != nil {
				return "", err
			}
			sum += n * weight[i]
		} else {
			return "", fmt.Errorf("不是数字")
		}
	}

	m := sum % 11
	return code[m], nil
}

func DesEncrypt(origData, key []byte) ([]byte, error) {
	block, err := des.NewCipher(key)
	if err != nil {
		return nil, err
	}
	origData = PKCS5Padding(origData, block.BlockSize())
	// origData = ZeroPadding(origData, block.BlockSize())
	blockMode := cipher.NewCBCEncrypter(block, key)
	crypted := make([]byte, len(origData))
	// 根据CryptBlocks方法的说明，如下方式初始化crypted也可以
	// crypted := origData
	blockMode.CryptBlocks(crypted, origData)
	return crypted, nil
}

func DesDecrypt(crypted, key []byte) ([]byte, error) {
	block, err := des.NewCipher(key)
	if err != nil {
		return nil, err
	}
	blockMode := cipher.NewCBCDecrypter(block, key)
	origData := make([]byte, len(crypted))
	// origData := crypted
	blockMode.CryptBlocks(origData, crypted)
	origData = PKCS5UnPadding(origData)
	// origData = ZeroUnPadding(origData)
	return origData, nil
}

//
//func PKCS5Padding(ciphertext []byte, blockSize int) []byte {
//	padding := blockSize - len(ciphertext)%blockSize
//	padtext := bytes.Repeat([]byte{byte(padding)}, padding)
//	return append(ciphertext, padtext...)
//}
//
//func PKCS5UnPadding(origData []byte) []byte {
//	length := len(origData)
//	// 去掉最后一个字节 unpadding 次
//	unpadding := int(origData[length-1])
//	return origData[:(length - unpadding)]
//}

func GetExistRoom(UserID int32) *map[string]interface{} {

	config := Get_AreaConfig("default")

	session := MongoSession(config)
	db := session.DB("") //数据库名称
	name := "mj_pre_rooms"

	uids := make([]int32, 0)
	uids = append(uids, UserID)

	collection := db.C(name) //如果该集合已经存在的话，则直接返回

	defer func() {
		session.Close()
		if err := recover(); err != nil {
			logger.Error(err)
		}
	}()

	inopt := bson.M{"$in": uids}
	uidopt := bson.M{"uids": inopt}

	result := make(map[string]interface{}, 0)
	err := collection.Find(&uidopt).One(&result)
	if err != nil {
		logger.Error(err)
		return nil
	}
	return &result
}
